package dawnland02.data.map.model;

import dawnland02.model.entity.Entity;
import java.util.*;

/**
 * User: Petru Obreja (obrejap@yahoo.com)
 * Date: Sep 15, 2009
 * Time: 7:15:59 PM
 */
public class Matrix {
    private final Integer rows;
    private final Integer columns;
    private final Map<Integer, MatrixCell> nodes;
    private final Map<Integer, Set<MatrixCell>> nodesByRows;
    private final Map<Integer, Set<MatrixCell>> nodesByColumns;

    public Matrix(Integer rows, Integer columns) {
        this.rows = rows;
        this.columns = columns;
        nodes = new HashMap<Integer, MatrixCell>();
        nodesByRows = new HashMap<Integer, Set<MatrixCell>>();
        nodesByColumns = new HashMap<Integer, Set<MatrixCell>>();
    }

    public Integer getRows() {
        return rows;
    }

    public Integer getColumns() {
        return columns;
    }

    public Map<Integer, MatrixCell> getNodes() {
        return nodes;
    }

    public MatrixCell[] getNodesAsArray() {
        return nodes.values().toArray(new MatrixCell[0]);
    }

    public MatrixCell getNode(Integer nodeId) {
        return nodes.get(nodeId);
    }

    public MatrixCell getNode(Integer x, Integer y) {
        if (x < 0 || x >= rows) {
            return null;
        }
        if (y < 0 || y >= columns) {
            return null;
        }
        Set<MatrixCell> column = nodesByColumns.get(x);
        if (column != null) { //column not created yet
            for (MatrixCell node : column) {
                if (node.getY().equals(y)) {
                    return node;
                }
            }
        }
        return null; //never here;
    }

    public Map<MatrixDirection,MatrixCell> getNeighbours(MatrixCell node) {
        Map<MatrixDirection,MatrixCell> neighbours = new HashMap<MatrixDirection,MatrixCell>();
        int oddRowXShift = 0;
        if (node.isEvenRow()) {
            oddRowXShift = 1;
        }
        MatrixCell nodeUpRight = getNode(node.getX() + oddRowXShift, node.getY() - 1);
        if (nodeUpRight != null) {
            neighbours.put(MatrixDirection.RIGHT_UP,nodeUpRight);
        }
        MatrixCell nodeRight = getNode(node.getX() + 1, node.getY());
        if (nodeRight != null) {
            neighbours.put(MatrixDirection.RIGHT,nodeRight);
        }
        MatrixCell nodeDownRight = getNode(node.getX() + oddRowXShift, node.getY() + 1);
        if (nodeDownRight != null) {
            neighbours.put(MatrixDirection.RIGHT_DOWN,nodeDownRight);
        }
        MatrixCell nodeDownLeft = getNode(node.getX() - 1 + oddRowXShift, node.getY() + 1);
        if (nodeDownLeft != null) {
            neighbours.put(MatrixDirection.LEFT_DOWN,nodeDownLeft);
        }
        MatrixCell nodeLeft = getNode(node.getX() - 1, node.getY());
        if (nodeLeft != null) {
            neighbours.put(MatrixDirection.LEFT,nodeLeft);
        }
        MatrixCell nodeUpLeft = getNode(node.getX() - 1 + oddRowXShift, node.getY() - 1);
        if (nodeUpLeft != null) {
            neighbours.put(MatrixDirection.LEFT_UP,nodeUpLeft);
        }
        return neighbours;
    }

    /*
     * return null if the two nodes are not neighbours
     */
    public MatrixDirection getMatrixDirectionBetweenTwoNeighbours(Integer firstMatrixCellId, Integer secondMatrixCellId){
        MatrixCell firstMatrixCell = getNode(firstMatrixCellId), secondMatrixCell = getNode(secondMatrixCellId);
        return getMatrixDirectionBetweenTwoNeighbours(firstMatrixCell, secondMatrixCell);
    }

    /*
     * return null if the two nodes are not neighbours
     */
    public MatrixDirection getMatrixDirectionBetweenTwoNeighbours(MatrixCell firstMatrixCell, MatrixCell secondMatrixCell){
        if((firstMatrixCell == null) || (secondMatrixCell == null)){
            return null;
        }
        int oddRowXShift = 0;
        if (firstMatrixCell.isEvenRow()) {
            oddRowXShift = 1;
        }
        if((firstMatrixCell.getX()+oddRowXShift == secondMatrixCell.getX()) && (firstMatrixCell.getY() == secondMatrixCell.getY()+1)){
            return MatrixDirection.RIGHT_UP;
        } else if((firstMatrixCell.getX() == secondMatrixCell.getX()-1) && (firstMatrixCell.getY() == secondMatrixCell.getY())){
            return MatrixDirection.RIGHT;
        } else if((firstMatrixCell.getX()+oddRowXShift == secondMatrixCell.getX()) && (firstMatrixCell.getY() == secondMatrixCell.getY()-1)){
            return MatrixDirection.RIGHT_DOWN;
        } else if((firstMatrixCell.getX()+oddRowXShift == secondMatrixCell.getX()+1) && (firstMatrixCell.getY() == secondMatrixCell.getY()-1)){
            return MatrixDirection.LEFT_DOWN;
        } else if((firstMatrixCell.getX() == secondMatrixCell.getX()+1) && (firstMatrixCell.getY() == secondMatrixCell.getY())){
            return MatrixDirection.LEFT;
        } else if((firstMatrixCell.getX()+oddRowXShift == secondMatrixCell.getX()+1) && (firstMatrixCell.getY() == secondMatrixCell.getY()+1)){
            return MatrixDirection.LEFT_UP;
        }
        return null;
    }

    public double computeDistanceBetweenTwoNodes(Entity entity, MatrixCell a, MatrixCell b) {
        double x = Math.abs(a.getX() - b.getX());
        double y = Math.abs(a.getY() - b.getY());
        return Math.sqrt(x * x + y * y);
    }

    public double computeCostBetweenTwoNodes(Entity entity, MatrixCell start, MatrixCell end) {
        if(getMatrixDirectionBetweenTwoNeighbours(start, end) == null){
            return Double.MAX_VALUE;
        }
        return start.getCost() + end.getCost();
    }

    public MatrixCell addNode(Integer id, Integer x, Integer y, Integer type, Integer roadLevel) {
        MatrixCell node = new MatrixCell(id, x, y, type, roadLevel);
        nodes.put(node.getId(), node);
        Set<MatrixCell> row = nodesByRows.get(y);
        if (row == null) {
            row = new LinkedHashSet<MatrixCell>();
            nodesByRows.put(y, row);
        }
        row.add(node);
        Set<MatrixCell> column = nodesByColumns.get(x);
        if (column == null) {
            column = new LinkedHashSet<MatrixCell>();
            nodesByColumns.put(x, column);
        }
        column.add(node);
        return node;
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < rows; i++) {
            Set<MatrixCell> row = nodesByRows.get(i);
            for (MatrixCell node : row) {
                stringBuilder.append(node.toBigString()).append(" ");
            }
            stringBuilder.append("\n");
        }
        return stringBuilder.toString();
    }
}
